﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Text.RegularExpressions;
using System.Web;

namespace ConsoleApplication
{
    class DropBoxInterop
    {
        private bool loggedIn;
        private string email;
        private string password;
        private CookieContainer _cookieContainer;
        private const string userAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.1.3) Gecko/20090824 Firefox/3.5.3 GTB5 (.NET CLR 3.5.30729)";

        public DropBoxInterop(string email, string password)
        {
            loggedIn = false;
            this.email = email;
            this.password = password;
            this._cookieContainer = new CookieContainer();
        }

        public void UploadFile(string name, byte[] bytes, string destFolder)
        {
            if (!loggedIn)
                Login();

            string homeResponse = MakeGetRequest("https://www.dropbox.com/home", this._cookieContainer, true);
            FixTheCookieContainer(ref this._cookieContainer, "a.www.dropbox.com", ".dropbox.com", true);

            if (homeResponse.Contains("login_submit"))
            {
                throw new Exception("somehow got logged out");
            }

            string nonce = ExtractNonceForUpload(homeResponse);

            Dictionary<string, object> multipartParams = new Dictionary<string,object>();
            multipartParams.Add("t", nonce);
            multipartParams.Add("plain", "yes");
            multipartParams.Add("dest", string.Concat("/Public/", destFolder));
            multipartParams.Add("file", new FormUpload.FileParameter(bytes, name, "image/jpeg"));

            HttpWebResponse response =
                FormUpload.MultipartFormDataPost("https://dl-web.dropbox.com/upload", "Mozilla 7.0", multipartParams, this._cookieContainer);
            response.Close();
            FixTheCookieContainer(ref this._cookieContainer, "a.www.dropbox.com", ".dropbox.com", true);
        }

        public void Login()
        {
            string nonce = ExtractNonceFromResponse(MakeGetRequest("https://www.dropbox.com/login", this._cookieContainer, true));
            FixTheCookieContainer(ref this._cookieContainer, "a.www.dropbox.com", ".dropbox.com", true);

            Dictionary<string,string> postParams = new Dictionary<string,string>();
            postParams.Add("login_email", email);
            postParams.Add("login_password", password );
            postParams.Add("t", nonce);
            string response = MakePostRequest("https://www.dropbox.com/login", postParams, this._cookieContainer);
            FixTheCookieContainer(ref this._cookieContainer, "a.www.dropbox.com", ".dropbox.com", true);

            if (!response.ToLower().Contains("location: /home"))
                throw new Exception("not logged in");

            this.loggedIn = true;
        }

        private static string MakeGetRequest(string uri, CookieContainer cooookie)
        {
            return MakeGetRequest(uri, cooookie, false);
        }

        private static string MakeGetRequest(string uri, CookieContainer cooookie, bool allowAuto)
        {
            int retryCount = 0;
            bool succeeded = false;

            while (retryCount < 3 && !succeeded)
            {
                HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
                request.CookieContainer = cooookie;
                //request.Proxy = new WebProxy("10.10.209.30", 8080);
                request.AllowAutoRedirect = allowAuto;
                request.ReadWriteTimeout = 6000;
                request.Timeout = 6000;
                request.UserAgent = userAgent;
                request.Referer = uri;
                request.Accept = "*/*";

                try
                {
                    using (WebResponse response = request.GetResponse())
                    {
                        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                        {
                            succeeded = true;
                            StringBuilder sb = new StringBuilder(1024);
                            foreach (string header in response.Headers)
                            {
                                sb.Append(header);
                                sb.Append(": ");
                                sb.Append(response.Headers[header]);
                                sb.Append("\r\n");
                            }
                            sb.Append("\r\n\r\n");
                            sb.Append(reader.ReadToEnd());

                            reader.Close();
                            reader.Dispose();
                            response.Close();

                            return sb.ToString();
                        }
                    }
                }
                catch (Exception e)
                {
                    WebException we = e as WebException;
                    Console.WriteLine("connections: " + request.ServicePoint.CurrentConnections);
                    request.ServicePoint.ConnectionLeaseTimeout = 0;
                    System.Threading.Thread.Sleep(200);
                    request.Abort();
                }
                Console.WriteLine("connections: " + request.ServicePoint.CurrentConnections);
                retryCount++;
            }

            return null;
        }

        private static string MakePostRequest(string uri, Dictionary<string,string> postParams, CookieContainer coooookie)
        {
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);
            request.Method = "POST";
            //request.Proxy = new WebProxy("10.10.209.30", 8080);
            request.ContentType = "application/x-www-form-urlencoded";
            request.CookieContainer = coooookie;
            request.UserAgent = userAgent;
            request.AllowAutoRedirect = false;
            request.ReadWriteTimeout = 6000;
            request.Timeout = 6000;
            request.Referer = uri;
            request.Accept = "*/*";

            int retryCount = 0;
            bool succeeded = false;

            while (retryCount < 3 && !succeeded)
            {
                try
                {
                    using (Stream s = request.GetRequestStream())
                    {
                        using (StreamWriter writer = new StreamWriter(s))
                        {
                            int count = 0;
                            foreach (KeyValuePair<string, string> postParam in postParams)
                            {
                                writer.Write(string.Format("{0}={1}", HttpUtility.UrlEncode(postParam.Key), HttpUtility.UrlEncode(postParam.Value)));
                                count++;
                                if (count < postParams.Count)
                                    writer.Write('&');
                            }
                        }
                        s.Close();
                    }

                    using (WebResponse response = request.GetResponse())
                    {
                        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
                        {
                            succeeded = true;

                            StringBuilder sb = new StringBuilder(1024);
                            foreach (string header in response.Headers)
                            {
                                sb.Append(header);
                                sb.Append(": ");
                                sb.Append(response.Headers[header]);
                                sb.Append("\r\n");
                            }
                            sb.Append("\r\n\r\n");
                            sb.Append(reader.ReadToEnd());

                            response.Close();

                            return sb.ToString();
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine("connections: " + request.ServicePoint.CurrentConnections);
                    Console.WriteLine(e);
                    request.Abort();
                }
                Console.WriteLine("connections: " + request.ServicePoint.CurrentConnections);
            }

            return null;
        }

        private static Regex nonceRegex = new Regex("<input\\s+type=\"hidden\"\\s+name=\"t\"\\s+value=\"([0-9a-f]+)\"\\s+");
        private static string ExtractNonceFromResponse(string response)
        {
            Match m = nonceRegex.Match(response);
            if (m.Groups.Count > 0)
            {
                return m.Groups[1].Value;
            }
            else 
                return null;
        }

        private static Regex uploadNonce = new Regex(@"<form action=""https://dl-web.dropbox.com/upload""[^>]*>[^<]*<input type=""hidden"" name=""[^""]*"" value=""([^""]*)""\s*/>");
        private static string ExtractNonceForUpload(string response)
        {
            Match m = uploadNonce.Match(response);
            if (m.Groups.Count > 0)
            {
                return m.Groups[1].Value;
            }
            else
                return null;
        }

        private void FixTheCookieContainer(ref CookieContainer container, string stupidDomain, string realDomain, bool isSSL)
        {
            CookieContainer newContainer = new CookieContainer();
            Uri notStupidUri = new Uri(string.Format("http{0}://{1}", isSSL ? "s" : "", (stupidDomain.StartsWith(".") ? string.Concat("a", stupidDomain) : stupidDomain)));
            CookieCollection cc = container.GetCookies(notStupidUri);
            foreach (var cookie in cc)
            {
                string cookieVal = cookie.ToString();
                int eqIdx = cookieVal.IndexOf("=");
                Cookie ck = new Cookie(cookieVal.Substring(0, eqIdx), cookieVal.Substring(eqIdx + 1), "/", realDomain);
                newContainer.Add(ck);
            }
            container = newContainer;
        }
    }
}
